package com.sneakxy.mybatis.commons.repository.config;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.builder.BuilderException;
import org.apache.ibatis.builder.CacheRefResolver;
import org.apache.ibatis.builder.ResultMapResolver;
import org.apache.ibatis.builder.annotation.MethodResolver;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLStatementBuilder;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

import com.sneakxy.mybatis.commons.dialect.Dialect;
import com.sneakxy.mybatis.commons.dialect.MySqlDialect;
import com.sneakxy.mybatis.commons.dialect.OracleDialect;
import com.sneakxy.mybatis.commons.repository.query.MybatisPageBoundSql;
import com.sneakxy.mybatis.commons.repository.query.MybatisSortBoundSql;
import com.sneakxy.mybatis.commons.utils.SnowflakeIdGenerator;

/**
 * @author 潜行的青衣
 */
public class MybatisCommonsConfiguration extends Configuration {
	
	private Map<String, XMLMapperBuilder> xmlMapperBuilderMap = new HashMap<String, XMLMapperBuilder>();
	
	private Dialect dialect;
	
	private SnowflakeIdGenerator snowflakeIdGenerator;

	public MybatisCommonsConfiguration() throws IllegalAccessException {
		//替换MapperRegistry
		FieldUtils.writeField(this, "mapperRegistry", new MybatisCommonsMapperRegistry(this), true);
	}
	
	@SuppressWarnings({ "rawtypes"})
	@Override
	public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
			Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
		BoundSql boundSqlProxy = boundSql;
		if(SqlCommandType.SELECT == mappedStatement.getSqlCommandType()) {
			if(parameterObject instanceof Map) {
				Pageable page = this.getPage(parameterObject);
				if(page != null) {
					//替换为分页语句
					boundSqlProxy = new MybatisPageBoundSql(this, boundSql, page, dialect);
				} else {
					//替换为排序
					Sort sort = this.getSort(parameterObject);
					if(sort != null) {
						boundSqlProxy = new MybatisSortBoundSql(this, boundSql, sort);
					}
				}
			}
		}
		return super.newStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSqlProxy);
	}
	
	@Override
	protected void buildAllStatements() {
		if (!incompleteResultMaps.isEmpty()) {
			synchronized (incompleteResultMaps) {
				Iterator<ResultMapResolver> it = incompleteResultMaps.iterator(); 
				while(it.hasNext()) {
					it.next().resolve();
					it.remove();
				}
			}
		}
		if (!incompleteCacheRefs.isEmpty()) {
			synchronized (incompleteCacheRefs) {
				Iterator<CacheRefResolver>  it = incompleteCacheRefs.iterator();
				while(it.hasNext()) {
					it.next().resolveCacheRef();
					it.remove();
				}
			}
		}
		if (!incompleteStatements.isEmpty()) {
			synchronized (incompleteStatements) {
				Iterator<XMLStatementBuilder> it = incompleteStatements.iterator();
				while(it.hasNext()) {
					it.next().parseStatementNode();
					it.remove();
				}
			}
		}
		if (!incompleteMethods.isEmpty()) {
			synchronized (incompleteMethods) {
				Iterator<MethodResolver> it = incompleteMethods.iterator();
				while(it.hasNext()) {
					it.next().resolve();
					it.remove();
				}
			}
		}
	}

	@SuppressWarnings("unchecked")
	private Pageable getPage(Object parameterObject) {
		Map<String, Object> parameters = (Map<String, Object>) parameterObject;
		Iterator<Entry<String, Object>> it = parameters.entrySet().iterator();
		Object param = null;
		Pageable page = null;
		while(it.hasNext()) {
			param = it.next().getValue();
			if(param instanceof Pageable) {
				page = (Pageable) param;
				break;
			}
		}
		return page;
	}
	
	@SuppressWarnings("unchecked")
	private Sort getSort(Object parameterObject) {
		Map<String, Object> parameters = (Map<String, Object>) parameterObject;
		Iterator<Entry<String, Object>> it = parameters.entrySet().iterator();
		Object param = null;
		Sort sort = null;
		while(it.hasNext()) {
			param = it.next().getValue();
			if(param instanceof Sort) {
				sort = (Sort) param;
				break;
			}
		}
		return sort;
	}
	
	@Override
	public void setEnvironment(Environment environment) {
		super.setEnvironment(environment);
		this.setDialectFromEnvironment(environment);
	}
	
	protected void setDialectFromEnvironment(Environment environment) {
		if(this.dialect == null) {
			try {
				Connection conn = environment.getDataSource().getConnection();
				DatabaseMetaData meta = conn.getMetaData();
				String database = meta.getDatabaseProductName();
				this.dialect = this.createDialect(database);
				conn.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	public void addXMLMapperBuilder(XMLMapperBuilder xmlMapperBuilder) throws IllegalArgumentException, IllegalAccessException {
		XPathParser parser = (XPathParser) FieldUtils
				.getDeclaredField(XMLMapperBuilder.class, "parser", true).get(xmlMapperBuilder);
		String namespace = parser.evalNode("/mapper").getStringAttribute("namespace");
		if(StringUtils.isBlank(namespace)) {
	        throw new BuilderException("Mapper's namespace cannot be empty");
	    }
		xmlMapperBuilderMap.put(namespace, xmlMapperBuilder);
	}
	
	public void parseXMLMapperBuilder(String namespace) {
		XMLMapperBuilder xmlMapperBuilder = xmlMapperBuilderMap.get(namespace);
		if(xmlMapperBuilder != null) {
			xmlMapperBuilder.parse();
			xmlMapperBuilderMap.remove(namespace);
		}
	}
	
	public Dialect getDialect() {
		return dialect;
	}

	public void setDialect(Dialect dialect) {
		this.dialect = dialect;
	}

	protected Dialect createDialect(String database) {
		if(StringUtils.equalsIgnoreCase("oracle", database)) {
			return new OracleDialect(this);
		}
		return new MySqlDialect(this);
	}

	public SnowflakeIdGenerator getSnowflakeIdGenerator() {
		return snowflakeIdGenerator;
	}

	public void setSnowflakeIdGenerator(SnowflakeIdGenerator snowflakeIdGenerator) {
		this.snowflakeIdGenerator = snowflakeIdGenerator;
	}

}
